package com.atomos.particle
{
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import com.atomos.system.ParticleSystem;
	import com.bourre.collection.Iterator;
	import com.bourre.collection.Set;
	import com.bourre.collection.WeakCollection;
	import com.bourre.log.PixlibStringifier;
	import com.atomos.utils.PointUtils;	

	/**
	 * The <code>ParticleManager</code> singleton  manages particles and systems
	 * storage.
	 * 
	 * <p>Basically the <code>ParticleManager</code> is the only class which should
	 * contains hard references to particles and systems. All other objects, even
	 * if they creates new objects will stores them as weak references and register
	 * them in the <code>ParticleManager</code>.</p>
	 * 
	 * @author Cédric Néhémie 
	 */
	final public class ParticleManager
	{
		/*-----------------------------------------------------------
				STATIC MEMBERS
		-------------------------------------------------------------*/
		
		private static var _oInstance : ParticleManager;
		
		/**
		 * Returns the singleton instance of the <code>ParticleManager</code> class.
		 * 
		 * @return <code>ParticleManager</code> singleton instance.
		 */
		public static function getInstance () : ParticleManager
		{
			if( _oInstance == null ) _oInstance = new ParticleManager();
			
			return _oInstance;
		}
		
		/**
		 * Destroys the singleton instance of the <code>ParticleManager</code> class. 
		 */
		public static function release () : void
		{
			_oInstance.clear();
			_oInstance = null;
		}
		
		/*-----------------------------------------------------------
				INSTANCE MEMBERS
		-------------------------------------------------------------*/	
		
		private var _cAllParticleSystems : Set;
		private var _cAllParticles : Set;
		
		/**
		 * Constructs a new <code>ParticleManager</code> instance.
		 */
		public function ParticleManager ()
		{
			init();
		}
		
		/**
		 * Adds a <code>ParticleSystem</code> into the <code>ParticleManager</code>.
		 * 
		 * <p>A <code>ParticleSystem</code> can only be inserted one time in the set.
		 * If you try to insert a system already contained in the set, the function don't
		 * do anything.</p>
		 * 
		 * @param e <code>ParticleSystem</code> to add in the <code>ParticleManager</code>
		 */
		public function addParticleSystem ( e : ParticleSystem ) : void
		{
			_cAllParticleSystems.add( e );
		}
		
		/**
		 * Removes the passed-in <code>ParticleSystem</code> from the <code>ParticleManager</code>.
		 * 
		 * <p>If the passed-in <code>ParticleSystem</code> isn't already contained in the
		 * <code>ParticleManager</code> the function don't do anything.</p>
		 * 
		 * @param e <code>ParticleSystem</code> to remove from the manager
		 */
		public function removeParticleSystem ( e : ParticleSystem ) : void
		{
			_cAllParticleSystems.remove( e );
		}
		
		/**
		 * Returns a collection of all <code>ParticleSystem</code> objects stored
		 * by the <code>ParticleManager</code>. The returned collection is a
		 * <code>WeakCollection</code> by the way all references are weaks. 
		 * 
		 * @return <code>WeakCollection</code> of <code>ParticleSystems</code>
		 */
		public function getParticleSystems () : WeakCollection
		{
			return new WeakCollection ( _cAllParticleSystems.toArray() );
		}
		
		/**
		 * Returns the number of <code>ParticleSystem</code> objects currently
		 * stored in the <code>ParticleManager</code>.
		 * 
		 * @return <code>Number</code> of <code>ParticleSystem</code> objects
		 * 		   currently stored in the <code>ParticleManager</code>.
		 */
		public function getParticleSystemsCount () : Number
		{
			return _cAllParticleSystems.size();
		}
		/**
		 * Returns the number of <code>EmissionStrategy</code> objects currently
		 * running in systems contained by the <code>ParticleManager</code>.
		 * 
		 * @return <code>Number</code> of <code>EmissionStrategy</code> objects currently
		 * 		   running in systems contained by the <code>ParticleManager</code>.
		 */
		public function getEmissionsCount () : Number
		{
			var i : Iterator = _cAllParticleSystems.iterator();
			var n : Number = 0;
			while( i.hasNext() )
			{
				n += ( i.next() as ParticleSystem ).getEmissionsCount();
			}
			return n;
		}
		
		/**
		 * Adds a <code>Particle</code> object in the <code>ParticleManager</code>
		 * 
		 * <p>A <code>Particle</code> can only be inserted one time in the set.
		 * If you try to insert a particle already contained in the set, the
		 * function don't do anything.</p>
		 * 
		 * @param p <code>Particle</code> to add in the <code>ParticleManager</code>
		 */
		public function addParticle ( p : Particle ) : void
		{
			_cAllParticles.add( p );
		}
		
		/**
		 * Removes the passed-in <code>Particle</code> from the <code>ParticleManager</code>.
		 * 
		 * <p>If the passed-in <code>Particle</code> isn't already contained in the
		 * <code>ParticleManager</code> the function don't do anything.</p>
		 * 
		 * @param p <code>Particle</code> to remove from the manager
		 */
		public function removeParticle ( p : Particle ) : void
		{
			_cAllParticles.remove( p );
		} 
		
		/**
		 * Returns a collection of all <code>Particle</code> objects stored
		 * by the <code>ParticleManager</code>. The returned collection is a
		 * <code>WeakCollection</code> by the way all references are weaks. 
		 * 
		 * @return <code>WeakCollection</code> of <code>Particle</code>
		 */
		public function getParticles () : WeakCollection
		{
			return new WeakCollection ( _cAllParticles.toArray() );
		}
		
		/**
		 * Returns the number of <code>Particle</code> objects currently
		 * stored in the <code>ParticleManager</code>.
		 * 
		 * @return <code>Number</code> of <code>Particle</code> objects
		 * 		   currently stored in the <code>ParticleManager</code>.
		 */
		public function getParticlesCount () : Number
		{
			return _cAllParticles.size();
		}
		
		/**
		 * Returns a collection of all <code>Particle<code> objects stored
		 * in the <code>ParticleManager<code> of whom type is the same than
		 * the passed-in one.
		 * 
		 * @param type <code>Class</code> to compare with the particles in
		 * 			   <code>ParticleManager</code>
		 * @return <code>WeakCollection</code> of <code>Particle</code> of the
		 * 		   the type in argument
		 */
		public function getParticlesByType ( type : Class ) : WeakCollection
		{
			var c : WeakCollection = new WeakCollection();
			var i : Iterator = _cAllParticles.iterator();
			
			while( i.hasNext() )
			{
				var p : Particle = i.next() as Particle;
				if( p is type )
				{
					c.add( p );
				}
			}
			return c;
		}
		
		/**
		 * Returns a collection of all <code>Particle<code> objects stored
		 * in the <code>ParticleManager<code> managed by the passed-in
		 * <code>ParticleSystem</code>.
		 * 
		 * @param e <code>ParticleSystem</code> of which we want to get particles
		 * @return <code>WeakCollection</code> of <code>Particle</code>
		 */
		public function getParticlesByParticleSystem ( e : ParticleSystem ) : WeakCollection
		{
			return e.getParticles();
		}
		
		/**
		 * Returns a collection of all <code>Particle<code> objects stored
		 * in the <code>ParticleManager<code> of whom type is the same than
		 * the passed-in one.
		 * 
		 * @param type <code>Class</code> to compare with the particles in
		 * 			   <code>ParticleManager</code>
		 * @return <code>WeakCollection</code> of <code>Particle</code> of the
		 * 		   the type in argument
		 */
		public function getParticlesByArea ( area : Rectangle ) : WeakCollection
		{
			var c : WeakCollection = new WeakCollection();
			var i : Iterator = _cAllParticles.iterator();
			
			while( i.hasNext() )
			{
				var p : Particle = i.next() as Particle;
				// TODO Refactor to use rect method instead of custom code
				if(	p.position.x >= area.left &&
					p.position.x <= area.right &&
					p.position.y >= area.top &&
					p.position.y <= area.bottom )
				{
					c.add( p );
				}
			}
			return c;
		}
		
		/**
		 * 
		 * @param p
		 * @return 
		 */
		public function getParticlesByDistanceFrom ( point : Point, distance : Number ) : WeakCollection
		{
			var c : WeakCollection = new WeakCollection();
			var i : Iterator = _cAllParticles.iterator();
			
			while( i.hasNext() )
			{
				var p : Particle = i.next() as Particle;
				var dist : Number = PointUtils.distance ( p.position, point );
				if(	dist <= distance )
				{
					c.add( p );
				}
			}
			return c;
		}
		
		/**
		 * 
		 * 
		 */
		public function startAllParticleSystems () : void
		{
			var i : Iterator = _cAllParticleSystems.iterator();
			
			while( i.hasNext() )
			{
				( i.next() as ParticleSystem ).start();
			}
		}
		
		/**
		 * 
		 * 
		 */
		public function stopAllParticleSystems () : void
		{
			var i : Iterator = _cAllParticleSystems.iterator();
			
			while( i.hasNext() )
			{
				( i.next() as ParticleSystem ).stop();
			}
		}
		
		/**
		 * 
		 * 
		 */
		public function clear () : void
		{
			stopAllParticleSystems();
			init();
		}
		
		/**
		 * 
		 * 
		 */
		public function init () : void
		{
			_cAllParticleSystems = new Set ( ParticleSystem );
			_cAllParticles = new Set ( Particle );
		}
		
		/**
		 * 
		 * @return 
		 * 
		 */
		public function toString () : String
		{
			return PixlibStringifier.stringify( this );
		}
	}
}